#include "aabbox3df.h"
#include "plane.h"
#include "line3df.h"
#include "ray3df.h"
namespace mt
{
	//! Default Constructor.
	aabbox3df::aabbox3df() : MinEdge(-1, -1, -1), MaxEdge(1, 1, 1) {}
	//! Constructor with min edge and max edge.
	aabbox3df::aabbox3df(const vec3f& min, const vec3f& max) : MinEdge(min), MaxEdge(max) {}
	//! Constructor with only one point.
	aabbox3df::aabbox3df(const vec3f& init) : MinEdge(init), MaxEdge(init) {}
	//! Constructor with min edge and max edge as single values, not vectors.
	aabbox3df::aabbox3df(float minx, float miny, float minz, float maxx, float maxy, float maxz) : MinEdge(minx, miny, minz), MaxEdge(maxx, maxy, maxz) {}

	// operators
	//! Equality operator
	/** \param other box to compare with.
	\return True if both boxes are equal, else false. */
	inline bool aabbox3df::operator==(const aabbox3df& other) const { return (MinEdge == other.MinEdge && other.MaxEdge == MaxEdge); }
	//! Inequality operator
	/** \param other box to compare with.
	\return True if both boxes are different, else false. */
	inline bool aabbox3df::operator!=(const aabbox3df& other) const { return !(MinEdge == other.MinEdge && other.MaxEdge == MaxEdge); }

	// functions

	//! Resets the bounding box to a one-point box.
	/** \param x X coord of the point.
	\param y Y coord of the point.
	\param z Z coord of the point. */
	void aabbox3df::reset(float x, float y, float z)
	{
		MaxEdge.set(x, y, z);
		MinEdge = MaxEdge;
	}

	//! Resets the bounding box.
	/** \param initValue New box to set this one to. */
	void aabbox3df::reset(const aabbox3df& initValue)
	{
		*this = initValue;
	}

	//! Resets the bounding box to a one-point box.
	/** \param initValue New point. */
	void aabbox3df::reset(const vec3f& initValue)
	{
		MaxEdge = initValue;
		MinEdge = initValue;
	}

	//! Adds a point to the bounding box
	/** The box grows bigger, if point was outside of the box.
	\param p: Point to add into the box. */
	void aabbox3df::addInternalPoint(const vec3f& p)
	{
		addInternalPoint(p.X, p.Y, p.Z);
	}

	//! Adds another bounding box
	/** The box grows bigger, if the new box was outside of the box.
	\param b: Other bounding box to add into this box. */
	void aabbox3df::addInternalBox(const aabbox3df& b)
	{
		addInternalPoint(b.MaxEdge);
		addInternalPoint(b.MinEdge);
	}

	//! Adds a point to the bounding box
	/** The box grows bigger, if point is outside of the box.
	\param x X coordinate of the point to add to this box.
	\param y Y coordinate of the point to add to this box.
	\param z Z coordinate of the point to add to this box. */
	void aabbox3df::addInternalPoint(float x, float y, float z)
	{
		if (x>MaxEdge.X) MaxEdge.X = x;
		if (y>MaxEdge.Y) MaxEdge.Y = y;
		if (z>MaxEdge.Z) MaxEdge.Z = z;

		if (x<MinEdge.X) MinEdge.X = x;
		if (y<MinEdge.Y) MinEdge.Y = y;
		if (z<MinEdge.Z) MinEdge.Z = z;
	}

	//! Get center of the bounding box
	/** \return Center of the bounding box. */
	vec3f aabbox3df::getCenter() const
	{
		return (MinEdge + MaxEdge) / 2;
	}

	//! Get extent of the box (maximal distance of two points in the box)
	/** \return Extent of the bounding box. */
	vec3f aabbox3df::getExtent() const
	{
		return MaxEdge - MinEdge;
	}

	//! Check if the box is empty.
	/** This means that there is no space between the min and max edge.
	\return True if box is empty, else false. */
	bool aabbox3df::isEmpty() const
	{
		return MinEdge.equals(MaxEdge);
	}

	//! Get the volume enclosed by the box in cubed units
	float aabbox3df::getVolume() const
	{
		const vec3f e = getExtent();
		return e.X * e.Y * e.Z;
	}

	//! Get the surface area of the box in squared units
	float aabbox3df::getArea() const
	{
		const vec3f e = getExtent();
		return 2 * (e.X*e.Y + e.X*e.Z + e.Y*e.Z);
	}

	//! Stores all 8 edges of the box into an array
	/** \param edges: Pointer to array of 8 edges. */
	void aabbox3df::getEdges(vec3f *edges) const
	{
		const mt::vec3f middle = getCenter();
		const mt::vec3f diag = middle - MaxEdge;

		/*
		Edges are stored in this way:
		Hey, am I an ascii artist, or what? :) niko.
		/3--------/7
		/ |       / |
		/  |      /  |
		1---------5   |
		|  /2- - -|- -6
		| /       |  /
		|/        | /
		0---------4/
		*/

		edges[0].set(middle.X + diag.X, middle.Y + diag.Y, middle.Z + diag.Z);
		edges[1].set(middle.X + diag.X, middle.Y - diag.Y, middle.Z + diag.Z);
		edges[2].set(middle.X + diag.X, middle.Y + diag.Y, middle.Z - diag.Z);
		edges[3].set(middle.X + diag.X, middle.Y - diag.Y, middle.Z - diag.Z);
		edges[4].set(middle.X - diag.X, middle.Y + diag.Y, middle.Z + diag.Z);
		edges[5].set(middle.X - diag.X, middle.Y - diag.Y, middle.Z + diag.Z);
		edges[6].set(middle.X - diag.X, middle.Y + diag.Y, middle.Z - diag.Z);
		edges[7].set(middle.X - diag.X, middle.Y - diag.Y, middle.Z - diag.Z);
	}

	//! Repairs the box.
	/** Necessary if for example MinEdge and MaxEdge are swapped. */
	void aabbox3df::repair()
	{
		float t;

		if (MinEdge.X > MaxEdge.X)
		{
			t = MinEdge.X; MinEdge.X = MaxEdge.X; MaxEdge.X = t;
		}
		if (MinEdge.Y > MaxEdge.Y)
		{
			t = MinEdge.Y; MinEdge.Y = MaxEdge.Y; MaxEdge.Y = t;
		}
		if (MinEdge.Z > MaxEdge.Z)
		{
			t = MinEdge.Z; MinEdge.Z = MaxEdge.Z; MaxEdge.Z = t;
		}
	}

	//! Calculates a new interpolated bounding box.
	/** d=0 returns other, d=1 returns this, all other values blend between
	the two boxes.
	\param other Other box to interpolate between
	\param d Value between 0.0f and 1.0f.
	\return Interpolated box. */
	aabbox3df aabbox3df::getInterpolated(const aabbox3df& other, float d) const
	{
		float inv = 1.0f - d;
		return aabbox3df((other.MinEdge*inv) + (MinEdge*d),
			(other.MaxEdge*inv) + (MaxEdge*d));
	}

	//! Determines if a point is within this box.
	/** Border is included (IS part of the box)!
	\param p: Point to check.
	\return True if the point is within the box and false if not */
	bool aabbox3df::isPointInside(const vec3f& p) const
	{
		return (p.X >= MinEdge.X && p.X <= MaxEdge.X &&
			p.Y >= MinEdge.Y && p.Y <= MaxEdge.Y &&
			p.Z >= MinEdge.Z && p.Z <= MaxEdge.Z);
	}

	//! Determines if a point is within this box and not its borders.
	/** Border is excluded (NOT part of the box)!
	\param p: Point to check.
	\return True if the point is within the box and false if not. */
	bool aabbox3df::isPointTotalInside(const vec3f& p) const
	{
		return (p.X > MinEdge.X && p.X < MaxEdge.X &&
			p.Y > MinEdge.Y && p.Y < MaxEdge.Y &&
			p.Z > MinEdge.Z && p.Z < MaxEdge.Z);
	}

	//! Check if this box is completely inside the 'other' box.
	/** \param other: Other box to check against.
	\return True if this box is completly inside the other box,
	otherwise false. */
	bool aabbox3df::isFullInside(const aabbox3df& other) const
	{
		return (MinEdge.X >= other.MinEdge.X && MinEdge.Y >= other.MinEdge.Y && MinEdge.Z >= other.MinEdge.Z &&
			MaxEdge.X <= other.MaxEdge.X && MaxEdge.Y <= other.MaxEdge.Y && MaxEdge.Z <= other.MaxEdge.Z);
	}

	//! Determines if the axis-aligned box intersects with another axis-aligned box.
	/** \param other: Other box to check a intersection with.
	\return True if there is an intersection with the other box,
	otherwise false. */
	bool aabbox3df::intersectsWithBox(const aabbox3df& other) const
	{
		return (MinEdge.X <= other.MaxEdge.X && MinEdge.Y <= other.MaxEdge.Y && MinEdge.Z <= other.MaxEdge.Z &&
			MaxEdge.X >= other.MinEdge.X && MaxEdge.Y >= other.MinEdge.Y && MaxEdge.Z >= other.MinEdge.Z);
	}

	//! Tests if the box intersects with a line
	/** \param line: Line to test intersection with.
	\return True if there is an intersection , else false. */
	bool aabbox3df::intersectsWithLine(const line3df& line) const
	{
		return intersectsWithLine(line.getMiddle(), line.getVector().normalize(),
			(float)(line.getLength() * 0.5));
	}

	//! Tests if the box intersects with a ray
	bool aabbox3df::intersectsWithRay(const ray3df& ray, const float len)const
	{
		return intersectsWithLine(ray.Position, ray.Direction, len);
	}

	//! Tests if the box intersects with a line
	/** \param linemiddle Center of the line.
	\param linevect Vector of the line.
	\param halflength Half length of the line.
	\return True if there is an intersection, else false. */
	bool aabbox3df::intersectsWithLine(const vec3f& linemiddle,
		const vec3f& linevect, float halflength) const
	{
		const vec3f e = getExtent() * (float)0.5;
		const vec3f t = getCenter() - linemiddle;

		if ((fabs(t.X) > e.X + halflength * fabs(linevect.X)) ||
			(fabs(t.Y) > e.Y + halflength * fabs(linevect.Y)) ||
			(fabs(t.Z) > e.Z + halflength * fabs(linevect.Z)))
			return false;

		float r = e.Y * (float)fabs(linevect.Z) + e.Z * (float)fabs(linevect.Y);
		if (fabs(t.Y*linevect.Z - t.Z*linevect.Y) > r)
			return false;

		r = e.X * (float)fabs(linevect.Z) + e.Z * (float)fabs(linevect.X);
		if (fabs(t.Z*linevect.X - t.X*linevect.Z) > r)
			return false;

		r = e.X * (float)fabs(linevect.Y) + e.Y * (float)fabs(linevect.X);
		if (fabs(t.X*linevect.Y - t.Y*linevect.X) > r)
			return false;

		return true;
	}

	//! Classifies a relation with a plane.
	/** \param plane Plane to classify relation to.
	\return Returns ISREL3D_FRONT if the box is in front of the plane,
	ISREL3D_BACK if the box is behind the plane, and
	ISREL3D_CLIPPED if it is on both sides of the plane. */
	EIntersectionRelation3D aabbox3df::classifyPlaneRelation(const plane& plane) const
	{
		vec3f nearPoint(MaxEdge);
		vec3f farPoint(MinEdge);

		if (plane.SND.Normal.X > (float)0)
		{
			nearPoint.X = MinEdge.X;
			farPoint.X = MaxEdge.X;
		}

		if (plane.SND.Normal.Y > (float)0)
		{
			nearPoint.Y = MinEdge.Y;
			farPoint.Y = MaxEdge.Y;
		}

		if (plane.SND.Normal.Z > (float)0)
		{
			nearPoint.Z = MinEdge.Z;
			farPoint.Z = MaxEdge.Z;
		}

		if (plane.SND.Normal.dotProduct(nearPoint) + plane.SND.D > (float)0)
			return ISREL3D_FRONT;

		if (plane.SND.Normal.dotProduct(farPoint) + plane.SND.D> (float)0)
			return ISREL3D_CLIPPED;

		return ISREL3D_BACK;
	}
}